#include "ota_http.h"
#include "ota_memchunk.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <openssl/sha.h>
#include <curl/curl.h>
#include "thirdparty/log/log.h"
#include "thirdparty/cJSON/cJSON.h"

static size_t ota_http_writeChunkCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
    MemChunk *chunk = userp;
    if (ota_memchunk_append(chunk, contents, size * nmemb) < 0)
    {
        return 0;
    }
    return size * nmemb;
}

static int ota_http_calculateSHA1(char *filename, char *digest)
{
    unsigned char buf[1024];
    unsigned char sha_digest[SHA_DIGEST_LENGTH];
    FILE *file = fopen(filename, "rb");
    if (file == NULL)
    {
        log_error("Failed to open file %s", filename);
        return -1;
    }

    SHA_CTX ctx;
    int result = 0;
    result |= SHA1_Init(&ctx);

    int len;
    while ((len = fread(buf, sizeof(buf), 1, file)) != 0)
    {
        result |= SHA1_Update(&ctx, buf, len);
    }

    result |= SHA1_Final(sha_digest, &ctx);

    if (result != 0)
    {
        fclose(file);
        return -1;
    }

    fclose(file);

    for (int i = 0; i < SHA_DIGEST_LENGTH; i++)
    {
        sprintf(digest + i * 2, "%02X", sha_digest[i]);
    }
    return 0;
}

/**
 * @brief 请求url，并将结果存放到chunk中
 *
 * @param url 请求网址
 * @param chunk 内存块
 * @return int 0成功 -1失败
 */
static int ota_http_requestToChunk(char *url, MemChunk *chunk)
{
    CURL *curl;
    curl = curl_easy_init();

    if (!curl)
    {
        log_warn("curl_easy_init failed");
        return -1;
    }

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ota_http_writeChunkCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, chunk);

    int result = curl_easy_perform(curl);

    if (result != CURLE_OK)
    {
        curl_easy_cleanup(curl);
        return -1;
    }

    long http_code;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    if (http_code != 200)
    {
        curl_easy_cleanup(curl);
        return -1;
    }
    curl_easy_cleanup(curl);
    return 0;
}

static int ota_http_requestToFile(char *url, FILE *file)
{
    CURL *curl;
    curl = curl_easy_init();

    if (!curl)
    {
        log_warn("curl_easy_init failed");
        return -1;
    }

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);

    int result = curl_easy_perform(curl);

    if (result != CURLE_OK)
    {
        curl_easy_cleanup(curl);
        return -1;
    }

    long http_code;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

    if (http_code != 200)
    {
        curl_easy_cleanup(curl);
        return -1;
    }
    curl_easy_cleanup(curl);
    return 0;
}

int ota_http_getVersion(char *url, Version *version)
{
    if (!version)
    {
        log_warn("Version is null");
        return -1;
    }

    // 通过HTTP请求url获取版本信息
    MemChunk chunk;
    memset(&chunk, 0, sizeof(MemChunk));
    if (ota_memchunk_init(&chunk) < 0)
    {
        log_warn("Not enough memory for memory chunk");
        return -1;
    }

    if (ota_http_requestToChunk(url, &chunk) < 0)
    {
        log_warn("Failed to request to %s", url);
        return -1;
    }

    // 解析chunk中的字符串
    cJSON *version_obj = cJSON_Parse(chunk.data);
    ota_memchunk_free(&chunk);

    if (version_obj == NULL)
    {
        log_warn("Failed to parse version info");
        return -1;
    }

    // 解析版本信息，并存放到版本结构体中
    cJSON *major_object = cJSON_GetObjectItem(version_obj, "major");
    cJSON *minor_object = cJSON_GetObjectItem(version_obj, "minor");
    cJSON *patch_object = cJSON_GetObjectItem(version_obj, "patch");
    if (!major_object || !minor_object || !patch_object)
    {
        log_warn("Failed to parse version info");
        cJSON_Delete(version_obj);
        return -1;
    }

    version->major = major_object->valueint;
    version->minor = minor_object->valueint;
    version->patch = patch_object->valueint;

    cJSON_Delete(version_obj);
    return 0;
}

int ota_http_downloadFirmware(char *firmware_url, char *digest_url, char *file)
{
    // 下载固件到文件
    FILE *firmware = fopen(file, "w");
    if (firmware == NULL)
    {
        log_error("Failed to open file %s", file);
        return -1;
    }

    if (ota_http_requestToFile(firmware_url, firmware) < 0)
    {
        log_error("Failed to download firmware");
        fclose(firmware);
        goto OTA_DOWNLOAD_FAIL;
    }
    fclose(firmware);
    // 下载固件校验文件到chunk

    MemChunk chunk;
    memset(&chunk, 0, sizeof(MemChunk));
    ota_memchunk_init(&chunk);
    if (ota_http_requestToChunk(digest_url, &chunk) < 0)
    {
        log_error("Failed to download firmware digest");
        goto OTA_DOWNLOAD_DIGEST_FAIL;
    }

    // 本地计算固件校验，并和chunk里的数据进行比较
    char local_firmware_digetst[SHA_DIGEST_LENGTH * 2 + 1];
    if (ota_http_calculateSHA1(file, local_firmware_digetst) < 0)
    {
        log_warn("Failed to calculate SHA1");
        goto OTA_DOWNLOAD_DIGEST_FAIL;
    }

    if (strncmp(local_firmware_digetst, chunk.data, 2 * SHA_DIGEST_LENGTH) != 0)
    {
        log_error("Firmware digest mismatch");
        goto OTA_DOWNLOAD_DIGEST_FAIL;
    }
    ota_memchunk_free(&chunk);

    return 0;

OTA_DOWNLOAD_DIGEST_FAIL:
    ota_memchunk_free(&chunk);
OTA_DOWNLOAD_FAIL:
    unlink(file);
    return -1;
}
